{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fda6cc41-5335-4e36-9c52-1ef8be5b752e",
   "metadata": {},
   "source": [
    "# **特斯拉股票价格上涨/下跌预测**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "0e858167-773c-4ba1-b463-d723210b7e64",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import  yfinance as yf\n",
    "from newsapi import NewsApiClient\n",
    "from datetime import datetime, timedelta\n",
    "import ta\n",
    "import re\n",
    "from textblob import TextBlob\n",
    "from sklearn.ensemble import  RandomForestClassifier\n",
    "#引入随机森林模型\n",
    "from sklearn.model_selection import train_test_split, cross_val_score\n",
    "from sklearn.metrics import classification_report,confusion_matrix, accuracy_score"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69a64580-82fe-4a41-955c-3e9e799036e3",
   "metadata": {},
   "source": [
    "### **1.1 获取股价数据并进行技术分析**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "2d194006-d008-4997-8503-11efb4f39684",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[*********************100%***********************]  1 of 1 completed\n"
     ]
    }
   ],
   "source": [
    "today = datetime.today().date()\n",
    "from_date = today - timedelta(days=29)\n",
    "\n",
    "tsla = yf.download('TSLA', start = '2025-04-17', end = '2025-05-17').reset_index()\n",
    "tsla.columns = tsla.columns.get_level_values(0)\n",
    "    \n",
    "tsla['ma_3'] = tsla['Close'].rolling(window=3).mean()\n",
    "tsla['volatility'] = tsla['Close'].rolling(window=7).std()\n",
    "tsla['rsi'] = ta.momentum.RSIIndicator(tsla['Close'], window=14).rsi()\n",
    "tsla['price_diff'] = tsla['Close'].diff()\n",
    "tsla['label'] = (tsla['price_diff'] > 0).astype(int)\n",
    "tsla['return_1d'] = tsla['Close'].pct_change(1)\n",
    "tsla['day_of_week'] = tsla['Date'].dt.dayofweek\n",
    "tsla['price_change_pct'] = tsla['Close'].pct_change()\n",
    "tsla['is_weekend'] = tsla['day_of_week'] >= 5\n",
    "tsla = pd.DataFrame(tsla)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87d22ee6-0f4d-42a4-b7d4-5ff69001c41a",
   "metadata": {},
   "source": [
    "### **1.2 引入宏观数据（纳斯达克指数、标普500指数）**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "id": "0b99e7d7-f8b8-4ff1-af60-4dee1306abd5",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[*********************100%***********************]  1 of 1 completed\n",
      "[*********************100%***********************]  1 of 1 completed\n"
     ]
    }
   ],
   "source": [
    "sp500 = yf.download('^GSPC', start = '2025-04-17', end = '2025-05-17')\n",
    "nasdaq = yf.download('^IXIC', start = '2025-04-17', end = '2025-05-17')\n",
    "sp500.columns = sp500.columns.get_level_values(0)\n",
    "nasdaq.columns = nasdaq.columns.get_level_values(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "934f5ba3-0f56-43b9-90e2-33c4f097067e",
   "metadata": {},
   "source": [
    "### **1.3 数据合并**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "id": "0e8e0065-3fee-4009-9c43-7256f8d1b7c7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 21 entries, 0 to 20\n",
      "Data columns (total 17 columns):\n",
      " #   Column            Non-Null Count  Dtype         \n",
      "---  ------            --------------  -----         \n",
      " 0   Date              21 non-null     datetime64[ns]\n",
      " 1   Close             21 non-null     float64       \n",
      " 2   High              21 non-null     float64       \n",
      " 3   Low               21 non-null     float64       \n",
      " 4   Open              21 non-null     float64       \n",
      " 5   Volume            21 non-null     int64         \n",
      " 6   ma_3              19 non-null     float64       \n",
      " 7   volatility        15 non-null     float64       \n",
      " 8   rsi               8 non-null      float64       \n",
      " 9   price_diff        20 non-null     float64       \n",
      " 10  label             21 non-null     int32         \n",
      " 11  return_1d         20 non-null     float64       \n",
      " 12  day_of_week       21 non-null     int32         \n",
      " 13  price_change_pct  20 non-null     float64       \n",
      " 14  is_weekend        21 non-null     bool          \n",
      " 15  nasdaq_close      21 non-null     float64       \n",
      " 16  sp500_close       21 non-null     float64       \n",
      "dtypes: bool(1), datetime64[ns](1), float64(12), int32(2), int64(1)\n",
      "memory usage: 2.6 KB\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "df_macro = pd.DataFrame({\n",
    "    'Date': sp500.index,\n",
    "    'nasdaq_close': nasdaq['Close'].values,\n",
    "    'sp500_close': sp500['Close'].values,\n",
    "})\n",
    "df_merge = tsla.merge(df_macro, on = 'Date', how = 'left')\n",
    "print(df_merge.info())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a6a10cb-9a02-4d69-aa18-d17544a6b61e",
   "metadata": {},
   "source": [
    "### **1.4 获取新闻数据**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7793c126-7065-4fff-b4f9-adcf27b0ba5f",
   "metadata": {},
   "source": [
    "**1.4.1 获取新闻数据**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "id": "5da599f6-66da-43b2-810e-ea31972bb7d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "api = NewsApiClient( api_key = '37f00a6497e54e4ca66251059030ac2f')\n",
    "articles = api.get_everything(q = 'Tesla OR tariff OR Pakistan-India conflict',language= 'en', from_param = str(from_date), to = str(today),sort_by = 'relevancy',\\\n",
    "                                 page_size=100)\n",
    "df_news = pd.DataFrame(articles['articles'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ed470c66-8cb9-4907-962d-b29a4a6a8fb2",
   "metadata": {},
   "source": [
    "**1.4.2 获取新闻具体内容**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "id": "7ef22699-3d14-4bc9-9cac-bf3176290ea1",
   "metadata": {},
   "outputs": [],
   "source": [
    "    def clean_text(text):\n",
    "        if pd.isnull(text):\n",
    "            return ''\n",
    "        text = re.sub(r'[^\\w\\s]','', text)\n",
    "        #[^\\w\\s] 匹配“不是字母数字或空格的内容”,'' 替换为“空”，也就是把标点符号、特殊字符都删掉。\n",
    "        return text.lower()\n",
    "df_news['content'] = (df_news['title']+ ''+df_news['description']).apply(clean_text)\n",
    "#定义一个新闻内容列，将新闻标题和内容进行合并，再清理合并内容后的标点符号以及空格\n",
    "df_news['polarity'] = df_news['content'].apply(lambda x : TextBlob(x).sentiment.polarity)\n",
    "#引入TextBlob自然语言分处理库进行情感分析，对新闻内容列进行分析\n",
    "df_news['sentiment'] = df_news['polarity'].apply(lambda x:'positive' if x>0 else ('negative' if x<0 else 'neutral'))\n",
    "#定义sentiment列，将polarity值大于0定义为正面情绪，小于0为负面情绪，0为中立情绪。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad45c0fd-e899-42bd-9694-7cbe24eb1f24",
   "metadata": {},
   "source": [
    "### **2.1 关于特斯拉公司的市场情绪分析**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "0fada64a-cd44-482b-abb2-522ba9ccdf12",
   "metadata": {},
   "outputs": [],
   "source": [
    "df_news['date'] = pd.to_datetime(df_news['publishedAt']).dt.date\n",
    "daily_sentiment = df_news.groupby('date')['polarity'].mean().reset_index()\n",
    "#根据日期分类，计算每天的情感分析值，再将date索引变回列名\n",
    "daily_sentiment.columns = ['Date', 'Sentiment']\n",
    "df_merge['Date'] = pd.to_datetime(df_merge['Date']).dt.date\n",
    "df_merge = df_merge.merge(right = daily_sentiment, left_on='Date',right_on= 'Date', how='left')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf04adeb-42d1-44c3-9a3a-97ba531f2fac",
   "metadata": {},
   "source": [
    "### **2.2 定义特征工程**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "8d985efb-2bbc-4161-be9f-3bbdca8ee384",
   "metadata": {},
   "outputs": [],
   "source": [
    "features = df_merge[['Sentiment', 'price_change_pct','volatility','ma_3','day_of_week']]\n",
    "target = df_merge['label'].loc[features.index]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ecb685e-0d02-4c21-b7c2-c4b6b9c5f7c3",
   "metadata": {},
   "source": [
    "### **3.1 训练模型**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "7a1ff7c5-8745-4bca-9ac1-21cafccb8e3d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: black;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: block;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 1ex;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>RandomForestClassifier(random_state=42)</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;RandomForestClassifier<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.5/modules/generated/sklearn.ensemble.RandomForestClassifier.html\">?<span>Documentation for RandomForestClassifier</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>RandomForestClassifier(random_state=42)</pre></div> </div></div></div></div>"
      ],
      "text/plain": [
       "RandomForestClassifier(random_state=42)"
      ]
     },
     "execution_count": 105,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.3, random_state=42)\n",
    "model = RandomForestClassifier(n_estimators=100, random_state=42)\n",
    "model.fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e129b547-1222-4f69-b4f7-ef633ef41bb2",
   "metadata": {},
   "source": [
    "### **3.2 预测和结果评估**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "id": "b5b2c33f-67c5-4802-a63b-044daea0148f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "准确率： 0.8571428571428571\n",
      "混淆矩阵：\n",
      " [[3 1]\n",
      " [0 3]]\n",
      "分类报告：\n",
      "               precision    recall  f1-score   support\n",
      "\n",
      "           0       1.00      0.75      0.86         4\n",
      "           1       0.75      1.00      0.86         3\n",
      "\n",
      "    accuracy                           0.86         7\n",
      "   macro avg       0.88      0.88      0.86         7\n",
      "weighted avg       0.89      0.86      0.86         7\n",
      "\n"
     ]
    }
   ],
   "source": [
    "y_pred = model.predict(X_test)\n",
    "print(\"准确率：\", accuracy_score(y_test, y_pred))\n",
    "print(\"混淆矩阵：\\n\", confusion_matrix(y_test, y_pred))\n",
    "print(\"分类报告：\\n\", classification_report(y_test, y_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eee8a5ec-9e0b-4e2f-8c68-66639f25e1ee",
   "metadata": {},
   "source": [
    "**1. 结果显示预测准确率为85.7%，表示整体预测有85.7%的样本是正确的。**\n",
    "\n",
    "**2. 混淆矩阵第一行（实际为 0，表示下跌）：被正确预测为 0 的有 3 个，被错误预测为 1 的有 0 个，第二行（实际为 1，表示上涨）：被正确预测为 1 的有 3 个，被错误预测为 0 的有 1 个**\n",
    "\n",
    "**3. 分类报告中，precision指的是“预测为该类中有多少是对的，recall是实际为该类中，有多少被预测对了，f1-score是二者的调和平均，f1-score 都是0.86，说明模型对 两个类别都比较平衡、没有严重偏向一边**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "id": "5e5998ae-12f5-4202-aec6-9e5e61550def",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "交叉验证平均准确率： 0.9099999999999999\n",
      "Feature importances:\n",
      " price_change_pct    0.482359\n",
      "Sentiment           0.201768\n",
      "volatility          0.152294\n",
      "ma_3                0.105132\n",
      "day_of_week         0.058448\n",
      "dtype: float64\n"
     ]
    }
   ],
   "source": [
    "# 交叉验证\n",
    "scores = cross_val_score(model, features, target, cv=5)\n",
    "print(\"交叉验证平均准确率：\", scores.mean())\n",
    "\n",
    "feat_imp = pd.Series(\n",
    "    model.feature_importances_,\n",
    "    index=features.columns\n",
    ").sort_values(ascending=False)\n",
    "\n",
    "print(\"Feature importances:\\n\", feat_imp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7526481d-a857-42a8-b755-30fae4a63f00",
   "metadata": {},
   "source": [
    "**结论：该模型在不同数据划分下的泛化能力强，预测结果稳定。**\n",
    "\n",
    "**特征重要性分析：**\n",
    "\n",
    "**1. price_change_pct重要性为48.2%，占比最大，表明说明前一日的涨跌幅是次日走势的关键预测因子。**\n",
    "\n",
    "**2. Sentiment重要性为20%，占比次之，说明新闻情绪确实对股价趋势有预测价值。**\n",
    "\n",
    "**3. volatility、ma_3和day_of_work占比依次递减，对股价影响逐渐减小。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b9a9d9b-a46a-4a2a-8ab6-4ce4ca644ba0",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:base] *",
   "language": "python",
   "name": "conda-base-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
